<?php
declare(strict_types=1);

namespace app\process;

use Workerman\Timer; 
use support\Redis;

/**
 * GPS 看门狗与告警进程
 *
 * 作用：
 * - 周期性检查接收侧/入库侧是否长时间空闲（无数据/无写库）
 * - 超过阈值时在日志打印并可通过 webhook 发送告警
 *
 * 参数（.env）：
 * - GPS_WATCH_ENABLE=1              是否启用看门狗（1 启用，0 关闭）
 * - GPS_WATCH_INTERVAL_MS=5000      检测周期（毫秒）
 * - GPS_ALERT_RECV_IDLE_SEC=60      接收空闲告警阈值（秒）
 * - GPS_ALERT_SAVE_IDLE_SEC=120     入库空闲告警阈值（秒）
 * - GPS_ALERT_WEBHOOK=              告警 webhook（可选，不填则仅日志）
 * - GPS_ALERT_DEDUP_SEC=300         告警去重窗口（秒），在该窗口内不重复发同类告警
 */
class GpsWatchdog
{
    /**
     * Worker 启动入口
     * - 按配置设置周期性检测任务
     */
    public function onWorkerStart(): void
    {
        $interval = (int)env('GPS_WATCH_INTERVAL_MS', 5000);      // 检测周期
        $recvIdle = (int)env('GPS_ALERT_RECV_IDLE_SEC', 60);       // 接收空闲阈值
        $saveIdle = (int)env('GPS_ALERT_SAVE_IDLE_SEC', 120);      // 写库空闲阈值
        $webhook  = (string)env('GPS_ALERT_WEBHOOK', '');          // webhook 地址
        $dedup    = (int)env('GPS_ALERT_DEDUP_SEC', 300);          // 去重窗口

        // 定时任务：周期性检查两类指标
        Timer::add($interval / 1000.0, function () use ($recvIdle, $saveIdle, $webhook, $dedup) {
            $this->check('recv_idle', 'gps:last_packet_at', $recvIdle, $webhook, $dedup); // 接收是否空闲
            $this->check('save_idle', 'gps:last_saved_at',  $saveIdle, $webhook, $dedup); // 入库是否空闲
        });
    }

    /**
     * 检查某项指标是否超过空闲阈值并告警
     * @param string $type   告警类型（recv_idle/save_idle）
     * @param string $key    Redis 键（时间戳或时间字符串）
     * @param int    $th     阈值秒
     * @param string $webhook webhook 地址（可为空）
     * @param int    $dedup  去重窗口秒
     */
    private function check(string $type, string $key, int $th, string $webhook, int $dedup): void
    {
        // 从 Redis 读取最后活动时间
        $raw = Redis::get($key);
        $last = is_numeric($raw) ? (int)$raw : strtotime((string)$raw); // 兼容字符串时间
        $idle = $last ? (time() - $last) : PHP_INT_MAX;                 // 计算空闲秒数

        // 超过阈值则考虑告警
        if ($last && $idle >= $th) {
            $token = "gps:alert:{$type}";                               // 告警去重键
            $lastSent = (int)Redis::get($token);               // 上次告警时间
            if ($lastSent === 0 || time() - $lastSent >= $dedup) {      // 去重窗口外才告警
                Redis::set($token, time());                    // 记录告警时间
                $payload = [
                    'type'       => $type,
                    'idle_sec'   => $idle,
                    'threshold'  => $th,
                    'last_at'    => $last ? date('Y-m-d H:i:s', $last) : '',
                ];
                echo "[ALERT] {$type} " . json_encode($payload, JSON_UNESCAPED_UNICODE) . "\n"; // 日志输出
                if ($webhook) {
                    $this->post($webhook, $payload);                    // 发送 webhook
                }
            }
        }
    }

    /**
     * 简易 webhook POST
     * @param string $url
     * @param array $data
     */
    private function post(string $url, array $data): void
    {
        // 使用 curl 发送 JSON，忽略返回
        $ch = curl_init($url);
        $body = json_encode($data, JSON_UNESCAPED_UNICODE);
        curl_setopt_array($ch, [
            CURLOPT_POST => true,
            CURLOPT_HTTPHEADER => ['Content-Type: application/json'],
            CURLOPT_POSTFIELDS => $body,
            CURLOPT_RETURNTRANSFER => true,
            CURLOPT_TIMEOUT => 5,
        ]);
        @curl_exec($ch);
        @curl_close($ch);
    }
}